package com.xbongbong.paas.controller.dingtalk;

import com.alibaba.dingtalk.openapi.exception.OApiException;
import com.alibaba.dingtalk.openapi.exception.OApiResultException;
import com.alibaba.dingtalk.openapi.helper.AuthHelper;
import com.alibaba.dingtalk.openapi.helper.Env;
import com.alibaba.dingtalk.openapi.helper.MicroAppHelper;
import com.alibaba.dingtalk.openapi.helper.ServiceHelper;
import com.alibaba.dingtalk.openapi.utils.aes.DingTalkEncryptException;
import com.alibaba.dingtalk.openapi.utils.aes.DingTalkEncryptor;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.dingtalk.api.response.OapiRobotSendResponse;
import com.xbongbong.callback.service.ConnectorConfigService;
import com.xbongbong.callback.service.ConnectorProducerService;
import com.xbongbong.paas.constant.PaasConstant;
import com.xbongbong.paas.domain.entity.PaasAppEntity;
import com.xbongbong.paas.enums.DelEnum;
import com.xbongbong.paas.enums.PlatFormEnum;
import com.xbongbong.paas.help.menu.InitMenuHelp;
import com.xbongbong.paas.model.PaasAppModel;
import com.xbongbong.paas.redis.PaasRedisHelper;
import com.xbongbong.paas.redis.RedisPrefixConstant;
import com.xbongbong.paas.service.IsvReceiveService;
import com.xbongbong.paas.service.PaasMenuService;
import com.xbongbong.paas.service.UserService;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.helper.PageHelper;
import com.xbongbong.paas.toolbox.util.BeanUtil;
import com.xbongbong.paas.toolbox.util.DateTimeUtil;
import com.xbongbong.paas.toolbox.util.DateUtil;
import com.xbongbong.paas.toolbox.util.PageHelperUtil;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.paas.toolbox.wrap.BaseVO;
import com.xbongbong.paas.toolbox.wrap.UserVO;
import com.xbongbong.paas.toolbox.wrap.XbbResponse;
import com.xbongbong.pro.constant.DingTalkPayConstant;
import com.xbongbong.pro.constant.XbbProConstant;
import com.xbongbong.pro.enums.PackageTypeEnum;
import com.xbongbong.pro.enums.errorcodes.SystemErrorCodeEnum;
import com.xbongbong.pro.menu.pojo.dto.MenuChangeStateDTO;
import com.xbongbong.pro.menu.pojo.dto.MenuInitDTO;
import com.xbongbong.pro.thread.ThreadPoolBeanConfig;
import com.xbongbong.pro.user.pojo.dto.SyncOrganizationDTO;
import com.xbongbong.pro.user.pojo.dto.SyncOrganizationForAdminDTO;
import com.xbongbong.saas.constant.BasicConstant;
import com.xbongbong.saas.constant.StringConstant;
import com.xbongbong.saas.enums.XbbRefTypeEnum;
import com.xbongbong.sys.domain.entity.CompanyConfigEntity;
import com.xbongbong.sys.domain.entity.CompanyEntity;
import com.xbongbong.sys.domain.entity.DepartmentEntity;
import com.xbongbong.sys.domain.entity.DingTalkPayLogEntity;
import com.xbongbong.sys.domain.entity.FeeCompanyEntity;
import com.xbongbong.sys.domain.entity.FeeEntity;
import com.xbongbong.sys.domain.entity.UserDepartmentEntity;
import com.xbongbong.sys.domain.entity.UserEntity;
import com.xbongbong.sys.enums.DdModelEnum;
import com.xbongbong.sys.enums.RoleEnum;
import com.xbongbong.sys.model.CompanyConfigModel;
import com.xbongbong.sys.model.CompanyModel;
import com.xbongbong.sys.model.DepartmentModel;
import com.xbongbong.sys.model.DingTalkPayLogModel;
import com.xbongbong.sys.model.FeeCompanyModel;
import com.xbongbong.sys.model.FeeModel;
import com.xbongbong.sys.model.UserDepartmentModel;
import com.xbongbong.sys.model.UserModel;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.Async;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static com.xbongbong.paas.redis.RedisPrefixConstant.DEPARTMENT_REFRESH_STATUS;
import static com.xbongbong.paas.redis.RedisTimeConstant.SYNC_STATUS_TIME;

/**
 * ISVReceiveController 这个controller用来接收钉钉服务器回调接口的推送
 * TODO 代码优化，日志记录,初始化部分包括模板仓库等初始化, 有些代码是迁移过来还没有优化，但是这块同步比较重要，
 * TODO 已经跑这么久了，稳妥起见后面再优化
 * @author kaka
 */
@RestController
@RequestMapping(XbbProConstant.API_ROOT_PREFIX + "/dingtalk")
public class IsvReceiveController {

	private static final Logger LOG = LoggerFactory.getLogger(IsvReceiveController.class);

	@Resource
	private PaasRedisHelper paasRedisHelper;
	@Resource
	private AuthHelper authHelper;
	@Resource
    private DingTalkPayLogModel dingTalkPayLogModel;
	@Resource
	private FeeModel feeModel;
	@Resource
	private ThreadPoolBeanConfig threadPoolBeanConfig;
	@Resource
	private IsvReceiveService isvReceiveService;
	@Resource
	private CompanyModel companyModel;
	@Resource
	private FeeCompanyModel feeCompanyModel;
	@Resource
	private CompanyConfigModel companyConfigModel;
	@Resource
	private DepartmentModel departmentModel;
	@Resource
	private UserModel userModel;
	@Resource
	private UserDepartmentModel userDepartmentModel;
	@Resource
	private ConnectorConfigService connectorConfigService;
	@Resource
	private ConnectorProducerService connectorProducerService;
	@Resource
	private PaasAppModel paasAppModel;
	@Resource
	private InitMenuHelp initMenuHelp;
	@Resource
	private UserService userService;
	@Resource
	private PaasMenuService paasMenuService;

	private static final String OXM_GOODS_CODE ="FW_GOODS-1000298770";


	/**
	 * 接收钉钉服务器的回调数据
	 */
	@ResponseBody
	@RequestMapping("/isvReceive")
	public String isvReceive(HttpServletRequest request)
			throws Exception {
		// 开始时间
		long startTime = System.currentTimeMillis();
		/* url中的签名 **/
		String msgSignature = request.getParameter("signature");
		/* url中的时间戳 **/
		String timeStamp = request.getParameter("timestamp");
		/* url中的随机字符串 **/
		String nonce = request.getParameter("nonce");

		/* post数据包数据中的加密数据 **/
		StringBuilder sb = new StringBuilder();
		try (ServletInputStream sis = request.getInputStream();
			 BufferedReader br = new BufferedReader(new InputStreamReader(sis))) {
			String line;
			while ((line = br.readLine()) != null) {
				sb.append(line);
			}
		} catch (Exception e) {
			LOG.error("isvReceive 流获取出错", e);
			//错误的回复
			return "";
		}

		/*
		 * post数据包数据中的加密数据转换成JSON对象，JSON对象的格式如下 { "encrypt":
		 * "1ojQf0NSvw2WPvW7LijxS8UvISr8pdDP+rXpPbcLGOmIBNbWetRg7IP0vdhVgkVwSoZBJeQwY2zhROsJq/HJ+q6tp1qhl9L1+ccC9ZjKs1wV5bmA9NoAWQiZ+7MpzQVq+j74rJQljdVyBdI/dGOvsnBSCxCVW0ISWX0vn9lYTuuHSoaxwCGylH9xRhYHL9bRDskBc7bO0FseHQQasdfghjkl"
		 * }
		 */
		JSONObject jsonEncrypt = JSONObject.parseObject(sb.toString());

		LOG.info("msgSignature:" + msgSignature);
		LOG.info("timeStamp:" + timeStamp);
		LOG.info("nonce:" + nonce);
		LOG.info("jsonEncrypt:" + JSON.toJSONString(jsonEncrypt));

		String encrypt = "";

		/* 取得JSON对象中的encrypt字段， **/
		try {
			encrypt = jsonEncrypt.getString("encrypt");
		} catch (Exception e) {
			LOG.error("获取encrypt字段出错", e);
		}

		/* 对encrypt进行解密 **/
		DingTalkEncryptor dingTalkEncryptor = null;
		String plainText = null;
		DingTalkEncryptException dingTalkEncryptException = null;
		try {
			/*
			 * 创建加解密类 第一个参数为注册套件的之时填写的token
			 * 第二个参数为注册套件的之时生成的数据加密密钥（ENCODING_AES_KEY）
			 * 第三个参数，ISV开发传入套件的suiteKey，普通企业开发传Corpid
			 * 具体参数值请查看开发者后台(http://console.d.aliyun.com)
			 *
			 * 注：其中，对于第三个参数，在第一次接受『验证回调URL有效性事件的时候』
			 * 传入Env.CREATE_SUITE_KEY，对于这种情况，已在异常中catch做了处理。
			 * 具体区别请查看文档『验证回调URL有效性事件』
			 */
			dingTalkEncryptor = new DingTalkEncryptor(Env.TOKEN,
					Env.ENCODING_AES_KEY, Env.SUITE_KEY);
			/*
			 * 获取从encrypt解密出来的明文
			 */
			plainText = dingTalkEncryptor.getDecryptMsg(msgSignature,
					timeStamp, nonce, encrypt);
		} catch (DingTalkEncryptException e) {

			dingTalkEncryptException = e;
			LOG.error("解密过程出错", e);
		} finally {
			if (dingTalkEncryptException != null) {
				if (dingTalkEncryptException.code == DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_CORPID_ERROR) {
					try {
						/*
						 * 第一次创建套件生成加解密类需要传入Env.CREATE_SUITE_KEY，
						 */
						dingTalkEncryptor = new DingTalkEncryptor(Env.TOKEN,
								Env.ENCODING_AES_KEY, Env.CREATE_SUITE_KEY);
						plainText = dingTalkEncryptor.getDecryptMsg(
								msgSignature, timeStamp, nonce, encrypt);
					} catch (DingTalkEncryptException e) {

						LOG.error("isvreceive接口访问时出错", e);
					}
				} else {
					LOG.error("dingTalkEncryptException else" + dingTalkEncryptException.getMessage());
					dingTalkEncryptException.printStackTrace();
				}
			}
		}

		LOG.info("isvReceive plainText = " + plainText);

		/*
		 * 对从encrypt解密出来的明文进行处理 不同的eventType的明文数据格式不同
		 */
		JSONObject plainTextJson = JSONObject.parseObject(plainText);
		final String corpId;
		String eventType = plainTextJson.getString("EventType");
		/*
		 * res是需要返回给钉钉服务器的字符串，一般为success
		 * "check_create_suite_url"和"check_update_suite_url"事件为random字段
		 * 具体请查看文档或者对应eventType的处理步骤
		 */
		String res = "success";
		Long now = DateTimeUtil.getInt();
		switch (eventType) {
		case "suite_ticket":
			/*
			 * "suite_ticket"事件每二十分钟推送一次,数据格式如下 { "SuiteKey": "suitexxxxxx",
			 * "EventType": "suite_ticket ", "TimeStamp": 1234456,
			 * "SuiteTicket": "adsadsad" }
			 */
			Env.suiteTicket = plainTextJson.getString("SuiteTicket");
			// 获取到suiteTicket之后需要换取suiteToken，
			String suiteToken = ServiceHelper.getSuiteToken(Env.SUITE_KEY,
					Env.SUITE_SECRET, Env.suiteTicket);
			/*
			 * ISV应当把最新推送的suiteTicket做持久化存储， 以防重启服务器之后丢失了当前的suiteTicket
			 */
			paasRedisHelper.setSuiteTokenValue(RedisPrefixConstant.DINGTALK_TICKET, "suiteTicket", Env.suiteTicket);
			paasRedisHelper.setSuiteTokenValue(RedisPrefixConstant.DINGTALK_TICKET, "suiteToken", suiteToken);
			break;
		case "tmp_auth_code":
			/*
			 * "tmp_auth_code"事件将企业对套件发起授权的时候推送 数据格式如下 { "SuiteKey":
			 * "suitexxxxxx", "EventType": " tmp_auth_code", "TimeStamp":
			 * 1234456, "AuthCode": "adads" }
			 * 此处增加销帮帮模拟请求处理逻辑，模拟请求与钉钉回调逻辑差别是增加了两个参数
			 * xbbFrom和permanentCode, xbbFrom为了标示销帮帮模拟请求，此时直接获取持久码
			 */
            // 获取当前的suiteToken
            Object value = paasRedisHelper.getSuiteTokenValue(RedisPrefixConstant.DINGTALK_TICKET, "suiteToken");
            if (value == null) {
                break;
            }
            String suiteTokenPerm = value.toString();
            LOG.info("tmp_auth_code suiteTokenPerm : " + suiteTokenPerm);

            String xbbFrom = plainTextJson.getString("xbbFrom");
            String permanentCode;
            if (StringUtil.isNotEmpty(xbbFrom) && "xbb".equals(xbbFrom)) {
                corpId = plainTextJson.getString("AuthCorpId").trim();
                permanentCode = plainTextJson.getString("permanentCode").trim();
            } else {
                /*
                 * 临时授权码使用一次即失效，没有必要保存，而且demo中这样做可能有并发问题
                 */
                String tmpAuthCode = plainTextJson.getString("AuthCode");
                /*
                 * 拿到tmp_auth_code（临时授权码）后，需要向钉钉服务器获取企业的corpId（企业id）和permanent_code（
                 * 永久授权码）
                 */
                JSONObject permanentJson = ServiceHelper.getPermanentCode(
                        tmpAuthCode, suiteTokenPerm);
                corpId = permanentJson.getJSONObject("auth_corp_info").getString(
                        "corpid");

                permanentCode = permanentJson.getString("permanent_code");

            }

			/*
			 * 将corpId（企业id）和permanent_code（永久授权码）做持久化存储
			 * 之后在获取企业的access_token时需要使用
			 */
			paasRedisHelper.setValuePermanent("permanentcode", corpId, permanentCode);

			/*
			 * 对企业授权的套件发起激活，
			 */
			ServiceHelper.getActivateSuite(suiteTokenPerm, Env.SUITE_KEY,
					corpId, permanentCode);

			/*
			 * 拿到permanent_code（永久授权码）后，向钉钉服务器获取企业的基本信息并插入库或更新数据库记录
			 */
			JSONObject authInfo = ServiceHelper.getAuthInfo(suiteTokenPerm,
					Env.SUITE_KEY, corpId, permanentCode);

			LOG.info("tmp_auth_code authInfo = " + authInfo);

			//先清除缓存，从接口拿agentId,因为agentId在应用重新授权时会变更
			paasRedisHelper.removeValue("dingAgentId", corpId);

			final String authAgentId = authHelper.getAgentId(corpId, Env.DING_XBB_APP_ID);

			CompanyEntity company = companyModel.getByKeyIgnoreDel(corpId);
			boolean insertCompany = false;
			if (company == null) {
				company = new CompanyEntity();
				insertCompany = true;
			}

			company.formatCompanyData(authInfo);

			company.setPermanentCode(permanentCode);

			// 前面一步对企业进行了激活，所以这里讲入库的企业设为正常状态
			company.setStatus(1);
			// 将公司del置为0
			company.setDel(0);
			// 设置agentId
			company.setAgentId(authAgentId);
			try {
				if (insertCompany) {
					companyModel.insert(company);
				} else {
					companyModel.update(company);
				}
			} catch (Exception e) {
				LOG.error("授权初始化过程出错", e);
			}

			/*
			 * 获取对应企业的access_token，每一个企业都会有一个对应的access_token，
			 * 访问对应企业的数据都将需要带上这个access_token access_token的过期时间为两个小时
			 */
			try {
				final String accessToken = authHelper.getAccessToken(corpId);
				/*
				 * 启动异步线程获取获取该公司最新的部门列表和员工列表，并把通讯录人数同步到company表
				 * @Async, 内部直接调用异步方法
				 */
                isvReceiveService.authCompany(xbbFrom, accessToken, company);

			} catch (OApiException e1) {
				LOG.error("授权初始化过程出错", e1);
			}
			// 统计时间
			if (Objects.equals(eventType, "tmp_auth_code") && corpId != null) {
				long endTime = System.currentTimeMillis();
				LOG.info("公司ID为  " + corpId + "的公司, 授权开通时间约为"
						+ (endTime - startTime) + "毫秒");
			}
			break;
		case "change_auth":
			/*
			 * "change_auth"事件将在企业授权变更消息发生时推送 数据格式如下 { "SuiteKey":
			 * "suitexxxxxx", "EventType": "change_auth", "TimeStamp": 1234456,
			 * "AuthCorpId": "xxxxx" }
			 */
			corpId = plainTextJson.getString("AuthCorpId");
			/*
			 * 当授权方（即授权企业）在钉钉手机客户端-微应用管理中，修改了对应用的授权企业通讯录范围后，
			 * 钉钉服务器会向服务提供商创建应用时填写的回调URL推送授权变更消息。
			 * 注意：推送的授权变更信息并不包括企业用户具体做了什么修改，所以收到推送之后，
			 * ISV需要通过调用“获取通讯录权限接口”查询新的授权范围。
			 */
			LOG.info("收到 change_auth 回调");
			CompanyEntity companyCA = companyModel.getByKeyIgnoreDel(corpId);
			if (companyCA == null) {
				break;
			}
      		 /*
			 * 获取对应企业的access_token，每一个企业都会有一个对应的access_token，
			 * 访问对应企业的数据都将需要带上这个access_token access_token的过期时间为两个小时
			 */
            try {
                final String accessToken = authHelper.getAccessToken(corpId);
				final String corpName = companyCA.getCorpName();
				/*
				 * 启动异步线程获取获取该公司最新的部门列表和员工列表
				 * @Async 内部直接调用异步方法
				 */
				isvReceiveService.changeAuth(accessToken, corpId, corpName);

            } catch (Exception e) {
				LOG.error("授权变更过程出错", e);
            }


			break;
		case "check_create_suite_url":
			/*
			 * "check_create_suite_url"事件将在创建套件的时候推送 {
			 * "EventType":"check_create_suite_url", "Random":"brdkKLMW",
			 * "TestSuiteKey":"suite4xxxxxxxxxxxxxxx" }
			 */
			// 此事件需要返回的"Random"字段，
			res = plainTextJson.getString("Random");
			// String testSuiteKey = plainTextJson.getString("TestSuiteKey");
			break;

		case "check_update_suite_url":
			/*
			 * "check_update_suite_url"事件将在更新套件的时候推送 {
			 * "EventType":"check_update_suite_url", "Random":"Aedr5LMW",
			 * "TestSuiteKey":"suited6db0pze8yao1b1y"
			 *
			 * }
			 */
			res = plainTextJson.getString("Random");
			break;
		case "suite_relieve":
			/*
			 * "suite_relieve" 此事件的推送会发生在企业解除套件授权的时候 数据格式如下 { "SuiteKey":
			 * "suitexxxxxx", "EventType": "suite_relieve", "TimeStamp":
			 * 1234456, "AuthCorpId": "xxxxx" }
			 */
			// 发生了"解除授权"事件之后，原来的永久授权码将立即失效
			LOG.info("收到 suite_relieve 回调");
			// 将永久授权码失效操作,将企业accessToken也失效
			corpId = plainTextJson.getString("AuthCorpId");
			LOG.info("suite_relieve corpId = " + corpId);
			paasRedisHelper.removeValue("accesstoken", corpId);
			paasRedisHelper.removeValue("permanentcode", corpId);

			// 将公司状态改为解除授权状态
			CompanyEntity unAuthCompany = companyModel.getByKeyIgnoreDel(corpId);
			if (unAuthCompany != null) {
				unAuthCompany.setUpdateTime(now);
				unAuthCompany.setStatus(-1);
				try {
					companyModel.update(unAuthCompany);
				} catch (Exception e) {
					LOG.error("companyService.update出错:", e);
				}
			}
			break;
		case "org_suite_relieve":
			LOG.info("收到 suite_relieve 回调");
			break;
		case "check_suite_license_code":
			/*
			 * "check_suite_license_code" 此事件的推送会发生在企业开通套件输入序列号的时候,要求请求立即返回
			 * 数据格式如下 { "SuiteKey": "suitexxxxxx", "EventType":
			 * "check_suite_license_code", "TimeStamp": 1234456, "AuthCorpId":
			 * "xxxxx", "LicenseCode": "xxxxx" }
			 */
			// 使用该套件企业的corpid
			corpId = plainTextJson.getString("AuthCorpID");
			// 企业使用套件输入的序列号
			String licensenCode = plainTextJson.getString("LicenseCode");
			/*
			 * 服务提供商在收到"校验序列号"事件推送后,判断该LicenseCode是否合法,如果合法返回加密"success"
			 */
			break;

		case "market_buy":
			/*
			 * "market_buy" 此事件的推送会发生在 用户在钉钉里购买销帮帮CRM 数据格式如下 { "EventType":
			 * "market_buy", "SuiteKey": "suitexdhgv7mn5ufoi9ui", "buyCorpId":
			 * "dingaba5244867492d3935c2f4657eb6378f", "goodsCode":
			 * "FW_GOODS-1000258333", "itemCode":
			 * "1c5f70cf04c437fb9aa1b209602cc886", "itemName": "按照范围收费规格0-300",
			 * fee_name "maxOfPeople": 300, "minOfPeople": 0, "orderId":
			 * 308356401790007, #fee_company_id , "paidtime": 1474535702000,
			 * #starttime "serviceStopTime": 1477065600000 #endtime ,/1000 }
			 */
			LOG.info("收到 market_buy 回调");
			LOG.info("market_buy===plainTextJson:" + plainTextJson);

			marketBuy(plainTextJson);

			break;
		case "goods_tryout":
			/**
			 * {
			 *     "buyerUnionId":"mLyKVGiiwEXQ5mT0Nwxxxx",
			 *     "suiteId":698002,
			 *     "corpId":"dingd678857e4250521135c2f465xxxx",
			 *     "fromDate":"2020-12-04 10:59:25",
			 *     "endDate":"2020-12-19 10:59:25",
			 *     "EventType":"goods_tryout",
			 *     "appId":1948,
			 *     "tryoutType":"enterprise_tryout",
			 *     "goodsCode":"FW_GOODS-100030xxxx",
			 *     "TimeStamp":"1607050765623"
			 * }
			 */
			LOG.info("收到 goods_tryout 回调 goods_tryout===plainTextJson:" + plainTextJson);
			String tryoutType = plainTextJson.getString("tryoutType");
			// 我们只处理个人试用，企业试用不管
			if(Objects.equals(tryoutType, "personal_tryout")){
				personalTryOut(plainTextJson);
			} else if (Objects.equals(tryoutType, "enterprise_tryout")){
				enterpriseTryout(plainTextJson);
			}
			break;
		case "org_micro_app_restore":
			/*
			 * 逻辑启用应用
			 * {
			 * "AgentId": 54146891,
			 * "AppId": 1949,
			 * "AuthCorpId": "ding9f50b15bccd1xxxxx",
			 * "EventType": "org_micro_app_restore",
			 * "SuiteKey": "suitexdhgv7mnxxxxx",
			 * "TimeStamp":"1481173967075"
			 * }
			 */
			LOG.info("收到 org_micro_app_restore 回调");
			orgMicroAppSwitch("org_micro_app_restore", plainTextJson);
			break;
		case "org_micro_app_stop":
			/*
			 * 逻辑启用应用
			 * {
			 * "AgentId": 54146891,
			 * "AppId": 1949,
			 * "AuthCorpId": "ding9f50b15bccd1xxxxx",
			 * "EventType": "org_micro_app_stop",
			 * "SuiteKey": "suitexdhgv7mnxxxxx",
			 * "TimeStamp":"1481173967075"
			 * }
			 */
			LOG.info("收到 org_micro_app_stop 回调");
			orgMicroAppSwitch("org_micro_app_stop", plainTextJson);
			break;
		case "main_data_subscribe":
			LOG.info("收到 连接器回调");
			connectorConfigService.save(plainTextJson);
			appInit(plainTextJson);
			corpId = plainTextJson.getString("AuthCorpId");
			connectorProducerService.historyDataInit(corpId);
			break;
		default:
			break;
		}

		/* 对返回信息进行加密 **/
		long timeStampLong = Long.parseLong(timeStamp);
		Map<String, String> jsonMap = null;
		try {
			/*
			 * jsonMap是需要返回给钉钉服务器的加密数据包
			 */
			jsonMap = dingTalkEncryptor.getEncryptedMap(res, timeStampLong, nonce);
		} catch (DingTalkEncryptException e) {
			LOG.error("dingTalkEncryptor.getEncryptedMap出错:", e);
		}
		JSONObject json = new JSONObject();
		json.putAll(jsonMap);

		return json.toJSONString();
	}

	/**
	 * 管理员授权
	 * @param plainTextJson
	 */
	private void enterpriseTryout(JSONObject plainTextJson) {
		try {
			String corpId = plainTextJson.getString("corpId");
			String userId = plainTextJson.getString("userid");
			Map<String, Object> param = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
			param.put(StringConstant.CORPID, corpId);
			param.put(StringConstant.DEL, DelEnum.NORMAL.getDel());
			param.put("feeName", "个人试用");
			param.put("authChannel", "个人试用");
			List<FeeCompanyEntity> feeCompanyEntities = feeCompanyModel.findEntitys(param);
			if (CollectionUtils.isEmpty(feeCompanyEntities)){
				return;
			}
			UserEntity adminUser = userModel.getByKey(userId, corpId);
			String adminName = Objects.isNull(adminUser) ? "" : adminUser.getName();
			for (FeeCompanyEntity feeCompanyEntity : feeCompanyEntities) {
				feeCompanyEntity.setMainCorpid("");
				feeCompanyEntity.setMemo(feeCompanyEntity.getMemo() + "管理员" + StringConstant.LEFT_BRACKETS + adminName + StringConstant.RIGHT_BRACKETS + "授权使用");
			}
			feeCompanyModel.updateBatch(feeCompanyEntities, corpId);
		} catch (Exception e) {
			LOG.error("enterpriseTryout事件出现了一个未知的错误  plainTextJson:" + JSON.toJSONString(plainTextJson), e);
		}
	}

	/**
	 * 应用初始化
	 * @param plainTextJson
	 */
	private void appInit(JSONObject plainTextJson) {
		String corpid = plainTextJson.getString("AuthCorpId");
		String modelId = plainTextJson.getString("MainEventModelId");

		//如果modelId是报销单的，则开启报销单
        if (Objects.equals(modelId, DdModelEnum.STANDARD_REIMBURSEMENT_MODEL.getModelId())) {
			try {
				openReimbursementApp(corpid);
			} catch (XbbException e) {
				LOG.error("应用信息生成失败，corpid:" + corpid + " modelId:" + modelId, e);
			}
		}
	}



	/**
	 * 开通报销单应用
	 * @param corpid
	 * @throws XbbException
	 * 创建时间 2021/10/18 3:49 PM
	 * 修改时间 2021/10/18 3:49 PM
	 * @author chy
	 */
	private void openReimbursementApp(String corpid) throws XbbException {
		String userId = null;
		List<String> userIds = userModel.getUserIdsByRoles(corpid, Arrays.asList(RoleEnum.ADMIN.getCode()));
		if (!userIds.isEmpty()) {
			userId = userIds.get(0);
		}
		PaasAppEntity paasAppEntity = paasAppModel.getByAlias(XbbRefTypeEnum.EXPENSE_MANAGEMENT.getAlias(), corpid);
		if (Objects.isNull(paasAppEntity)) {
			//初始化菜单、模板等
			MenuInitDTO menuInitDTO = new MenuInitDTO();
			menuInitDTO.setCorpid(corpid);
			menuInitDTO.setUserId(userId);
			menuInitDTO.setAlias(XbbRefTypeEnum.EXPENSE_MANAGEMENT.getAlias());
			menuInitDTO.setCompanyInit(0);
			initMenuHelp.menuInit(menuInitDTO);
		} else {
			MenuChangeStateDTO menuChangeStateDTO = new MenuChangeStateDTO();
			UserVO userVO = userService.getUserVO(corpid, userId, true, true);
			menuChangeStateDTO.setAppId(paasAppEntity.getId());
			menuChangeStateDTO.setEnable(true);
			menuChangeStateDTO.setCorpid(corpid);
			menuChangeStateDTO.setUserId(userId);
			menuChangeStateDTO.setLoginUser(userVO);
			menuChangeStateDTO.setLoginUserName(userVO.getName());
			menuChangeStateDTO.setPlatform(PlatFormEnum.WEB.getValue());
			menuChangeStateDTO.setTarget(MenuChangeStateDTO.TargetEnum.APP.getCode());
			paasMenuService.changeState(menuChangeStateDTO);
		}
	}

	@RequestMapping("/marketBuyTest")
	public String marketBuyTest(HttpServletRequest request,
                                HttpServletResponse response, Map<String, Object> modelMap)
			throws Exception {
		String jsonStr0 = "{\"itemName\":\"试用规格\",\"distributorCorpName\":\"chy网络科技有限公司\",\"EventType\":\"market_buy\",\"goodsCode\":\"FW_GOODS-1000298770\",\"orderCreateSource\":\"DRP\",\"discount\":1.0,\"buyCorpId\":\"chy1\",\"paidtime\":1528560000000,\"maxOfPeople\":5,\"serviceStopTime\":1531152000000,\"payFee\":0,\"distributorCorpId\":\"12345\",\"SuiteKey\":\"suitezzbrrbl1iykt5xw0\",\"minOfPeople\":0,\"orderId\":001,\"itemCode\":\"5681dfc2c7f338d493d35fccca7e1ee5\"}";
		String jsonStr1 = "{\"itemName\":\"N年套餐\",\"distributorCorpName\":\"chy网络科技有限公司\",\"EventType\":\"market_buy\",\"goodsCode\":\"FW_GOODS-1000298770\",\"orderCreateSource\":\"DRP\",\"discount\":1.0,\"buyCorpId\":\"chy1\",\"paidtime\":1528560000000,\"maxOfPeople\":10,\"serviceStopTime\":1562688000000,\"payFee\":14400,\"distributorCorpId\":\"12345\",\"SuiteKey\":\"suitezzbrrbl1iykt5xw0\",\"minOfPeople\":0,\"orderId\":002,\"itemCode\":\"5681dfc2c7f338d493d35fccca7e1ee5\"}";
		String jsonStr2 = "{\"itemName\":\"N年套餐\",\"distributorCorpName\":\"chy网络科技有限公司\",\"EventType\":\"market_buy\",\"goodsCode\":\"FW_GOODS-1000298770\",\"orderCreateSource\":\"DRP\",\"discount\":1.0,\"buyCorpId\":\"chy1\",\"paidtime\":1528560000000,\"maxOfPeople\":20,\"serviceStopTime\":1562688000000,\"payFee\":14400,\"distributorCorpId\":\"12345\",\"SuiteKey\":\"suitezzbrrbl1iykt5xw0\",\"minOfPeople\":0,\"orderId\":002,\"itemCode\":\"5681dfc2c7f338d493d35fccca7e1ee5\"}";
		String jsonStr3 = "{\"itemName\":\"N年套餐\",\"distributorCorpName\":\"chy网络科技有限公司\",\"EventType\":\"market_buy\",\"goodsCode\":\"FW_GOODS-1000298770\",\"orderCreateSource\":\"DRP\",\"discount\":1.0,\"buyCorpId\":\"chy1\",\"paidtime\":1528560000000,\"maxOfPeople\":5,\"serviceStopTime\":1594310400000,\"payFee\":14400,\"distributorCorpId\":\"12345\",\"SuiteKey\":\"suitezzbrrbl1iykt5xw0\",\"minOfPeople\":0,\"orderId\":002,\"itemCode\":\"5681dfc2c7f338d493d35fccca7e1ee5\"}";
		String jsonStr4 = "{\"itemName\":\"N年套餐\",\"distributorCorpName\":\"chy网络科技有限公司\",\"EventType\":\"market_buy\",\"goodsCode\":\"FW_GOODS-1000298770\",\"orderCreateSource\":\"DRP\",\"discount\":1.0,\"buyCorpId\":\"chy1\",\"paidtime\":1528560000000,\"maxOfPeople\":20,\"serviceStopTime\":1594310400000,\"payFee\":14400,\"distributorCorpId\":\"12345\",\"SuiteKey\":\"suitezzbrrbl1iykt5xw0\",\"minOfPeople\":0,\"orderId\":002,\"itemCode\":\"5681dfc2c7f338d493d35fccca7e1ee5\"}";
		// 内购
		String jsonStr5 = "{\"EventType\":\"market_buy\",\"orderId\":313379200700373,\"itemCode\":\"DD_I-159005\",\"subQuantity\":1,\"itemName\":\"周期规格6\",\"payFee\":1,\"serviceStopTime\":1571068800000,\"appId\":\"123\",\"goodsCode\":\"DD_GOODS-102\",\"goodsName\":\"内购商品AA\",\"paidtime\":1555307607000,\"SuiteKey\":\"suiteken\",\"buyCorpId\":\"ding45b220254ed2e97335c2f4657eb6378f\"}";
String jsonStr6 = "{\"orderType\":\"BUY\",\"unionId\":\"hV2BoQCpK9fuC38xua7MUgiEiE\",\"EventType\":\"market_buy\",\"orderId\":209357137280461,\"openId\":\"hV2BoQCpK9fuC38xua7MUgiEiE\",\"itemCode\":\"DT_GOODS_881604625849598_226009\",\"saleModelType\":\"CYC_UPGRADE\",\"orderChargeType\":\"TRYOUT\",\"maxOfPeople\":2147483647,\"itemName\":\"试用规格\",\"payFee\":0,\"serviceStopTime\":1623945600000,\"serviceStartTime\":1622707383000,\"goodsCode\":\"DT_GOODS_881604625849598\",\"mainCorpId\":\"ding7043ec303eb001d5ffe93478753d9884\",\"goodsName\":\"销帮帮测试版\",\"paidtime\":1622707383000,\"SuiteKey\":\"suitedqkmizfcjfg4pali\",\"minOfPeople\":0,\"buyCorpId\":\"ding09c06ccaa852a1e24ac5d6980864d335\"}";
		JSONObject json = JSON.parseObject(jsonStr6);
		marketBuy(json);
		return null;
	}

	private boolean marketBuy(JSONObject plainTextJson) {
		try {
			String buyCorpId = plainTextJson.getString("buyCorpId");
			String goodsCode = plainTextJson.getString("goodsCode");
			// fee_id
			String itemCode = plainTextJson.getString("itemCode");
			// fee_name
			String itemName = plainTextJson.getString("itemName")==null?"":plainTextJson.getString("itemName");
			Integer maxOfPeople = plainTextJson.getInteger("maxOfPeople");
			Integer minOfPeople = plainTextJson.getInteger("minOfPeople");
			// fee_company_id
			String orderId = plainTextJson.getString("orderId");
			// startTime
			Long paidtime = plainTextJson.getLong("paidtime")/1000 ;
			// endTime
			Long serviceStopTime = plainTextJson.getLong("serviceStopTime")/1000;

			//这些是价格相关的参数，需要补充处理 https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7386797.0.0.whJyMn&source=search&treeId=175&articleId=104945&docType=1#s2
			//订单创建来源，如果来自钉钉分销系统，则值为“DRP”,
			String orderCreateSource = plainTextJson.getString("orderCreateSource");
			// 正常应支付金额，钉钉分销系统提单价，以分为单位
			Integer nominalPayFee = plainTextJson.getInteger("nominalPayFee");
			// 折扣减免费用，以分为单位
			Integer discountFee = plainTextJson.getInteger("discountFee") ;
			// 订单折扣
			Float discount = plainTextJson.getFloat("discount") ;
			// 代理商公司名
			String distributorCorpName =  plainTextJson.getString("distributorCorpName") ;
			// 代理商公司id
			String distributorCorpId =  plainTextJson.getString("distributorCorpId");
			//以上项是分销系统接口过来才有的参数

			// 支付金额，以分为单位
			Float payFee = plainTextJson.getFloat("payFee") ;
			String mainCorpid = plainTextJson.getString("mainCorpId");
            CompanyEntity companyEntity = companyModel.getByKey(buyCorpId);
            if ( Objects.nonNull(companyEntity) && Objects.nonNull(mainCorpid)) {
                companyEntity.setMainCorpid(mainCorpid);
                companyModel.update(companyEntity);
            }
            if (StringUtil.isNotEmpty(mainCorpid)){
                itemName = "个人试用";
                distributorCorpName = "个人试用";
            }
			FeeCompanyEntity feeCompany = new FeeCompanyEntity();
			// 判断goodsCode DT  DD开头 会兵 内购订单与平常的订单区分，这里直接return
            // 试用规格提前做一些区分
			String originalArticleCode = plainTextJson.getString("originalArticleCode");
			if (Objects.equals(goodsCode, "DT_GOODS_881604625849598")) {

			} else if (Objects.equals(OXM_GOODS_CODE, originalArticleCode)) {//如果为OXM，不走钉钉内购逻辑
				feeCompany.setIsPay(1);
				feeCompany.setAuthChannel("OXM");
			} else {
				if (StringUtil.isNotEmpty(goodsCode) && (goodsCode.startsWith("DD") || goodsCode.startsWith("DT"))) {
					// 钉钉内购原始订单保存
					return dingTalkPay(plainTextJson);
				}
			}

			// 内购订单不需要在feeCompany表重复保存 会兵 原订单：goodsCode":"FW_GOODS-1000298770"
			nominalPayFee=nominalPayFee ==null?0:nominalPayFee;
			discountFee=discountFee ==null?0:discountFee;
			payFee=payFee ==null?0:payFee;
			discount=discount ==null?0:discount;
			//如果异常则允许5人
			maxOfPeople=maxOfPeople ==null?5:maxOfPeople;

			//如果maxOfPeople大于20w，钉钉对于不限人数套餐的定义，是最大int，我们限制为20w，否则多个套餐叠加时会溢出
			maxOfPeople= (maxOfPeople>200000 ?200000:maxOfPeople);

			//实际是多少月可用
			Long dayNum=( serviceStopTime -paidtime )/86400/30;

			//转换为元
			Float realPrice = (nominalPayFee+discountFee)/100f;
			Float fee = payFee/100f;
			Float feeDingding = payFee/100f;
			//如果，nominalPayFee大于0，则是来自分销系统 , nominalPayFee才是提单价，payFee可能为0（线下支付），也可能等于nominalPayFee
			if(nominalPayFee >0){
				fee=nominalPayFee/100f;
			}else{
				//用户钉钉直接支付，之前计算的realprice为0，应该等于fee chenhb 20170804
				realPrice=fee;
			}
			StringBuilder memoBuffer = new StringBuilder();
			memoBuffer.append("订单总价：").append(realPrice)
					.append(",折扣：").append(discount)
					.append(",优惠金额：").append(discountFee).append("，应支付金额：").append(fee)
					.append(",最终实际支付金额：").append(payFee);

			Map<String, Object> param = new HashMap<String, Object>();

			param.clear();
			param.put("del", 0);
			param.put("orderId", orderId);
			Integer entitysCount=feeCompanyModel.getEntitysCount(param);
			if(entitysCount>1){
				LOG.error("orderId：" + orderId + " 重复！");
				return false;
			}
			FeeCompanyEntity feeCompanyOld;
			param.clear();
			param.put("del", 0);
			param.put("corpid", buyCorpId);
			param.put("start", 0);
			param.put("pageNum", 1);
			param.put("orderByStr", "  fee asc ");
			List<FeeCompanyEntity> feeCompanyList= feeCompanyModel.findEntitys(param);
			if(feeCompanyList.isEmpty()){
				feeCompanyOld  = new FeeCompanyEntity();
				feeCompanyOld.setCorpName("");
			}else{
				feeCompanyOld = feeCompanyList.get(0);
			}

			//付费套餐
			feeCompany.setCorpName(feeCompanyOld.getCorpName());
			if(itemName.contains(PackageTypeEnum.ULTIMATE.getName())){
				// 旗舰版
				feeCompany.setFeeType(PackageTypeEnum.ULTIMATE.getType());
			} else if (itemName.contains(PackageTypeEnum.ADVANCED.getName())) {
				// 高级版
				feeCompany.setFeeType(PackageTypeEnum.ADVANCED.getType());
			} else {
				// 标准版
				feeCompany.setFeeType(PackageTypeEnum.STANDARD.getType());
			}

			feeCompany.setCorpid(buyCorpId);
            feeCompany.setMainCorpid(mainCorpid);
			feeCompany.setFeeId(0);
			feeCompany.setFeeName(itemName);
			//来自钉钉接口
			feeCompany.setRealPrice(realPrice);
			feeCompany.setBuyNum(1);
			//来自钉钉接口
			feeCompany.setFee(fee);

			feeCompany.setUserNum(maxOfPeople);
			feeCompany.setDayNum(dayNum.intValue());
			feeCompany.setIsActive(1);
			feeCompany.setIsInvoice(1);
			feeCompany.setOpUserName("");
			feeCompany.setOrderId(orderId);
			// 毫秒的话除1000
			feeCompany.setPayTime(paidtime);
			feeCompany.setStartTime(paidtime);
			feeCompany.setEndTime(serviceStopTime);
			// 钉钉支付
			feeCompany.setPayType(2);
			feeCompany.setAddTime(DateUtil.getInt());
			feeCompany.setUpdateTime(DateUtil.getInt());
			feeCompany.setMemo(memoBuffer.toString());

			distributorCorpName= (distributorCorpName == null)?  "" : distributorCorpName;
            //栗子来源，可能的渠道有：工作台-应用图标推荐；广场-专题等
            String authChannel = plainTextJson.getString("leadsFrom");
            if ( StringUtil.isNotEmpty(distributorCorpName) ){
                authChannel = distributorCorpName;//开通的渠道
            }else {
                //栗子来源，可能的渠道有：工作台-应用图标推荐；广场-专题等
                authChannel = plainTextJson.getString("leadsFrom");
                if ( StringUtil.isNotEmpty(authChannel) ) {
                    authChannel= "leads_"+authChannel ;
                }
                String solutionPackageName = plainTextJson.getString("solutionPackageName");
                if ( StringUtil.isNotEmpty(solutionPackageName) ) {
                    authChannel += solutionPackageName;
                }
                String mainCorpId = plainTextJson.getString("mainCorpId");
                if ( StringUtil.isNotEmpty(mainCorpId) ) {
                    authChannel= (authChannel == null)?  "个人试用" : authChannel +"_个人试用";
                }
            }
			//开通的渠道
			feeCompany.setAuthChannel(authChannel);

			feeCompany.setOrderType(feeCompanyModel.getOrderType(feeDingding,maxOfPeople,serviceStopTime,buyCorpId, true));
			feeCompany.setFeeInvoice(FeeCompanyEntity.getFeeInvoice(feeDingding,distributorCorpId,orderCreateSource));
			feeCompany.addMarketBuyJson(plainTextJson.toJSONString());
			feeCompany.setDrp(orderCreateSource);
			feeCompany.setFeeDingding(feeDingding);
			feeCompany.setUserNumDingding(maxOfPeople);

			//服务停止时间
			feeCompany.setServiceStopTime(serviceStopTime);
			//退款数据初始化
			feeCompany.setRefundStatus(0);
			feeCompany.setRefundTime(0);
			feeCompany.setRefundAmount(0f);
            setIsPay(plainTextJson, feeCompany);
			freePackage(plainTextJson, feeCompany);
			if (Objects.equals(OXM_GOODS_CODE, originalArticleCode)) {//如果为OXM，则ispay为1，AuthChannel为OXM
//				feeCompany.setIsPay(1);
				feeCompany.setAuthChannel("OXM");
			}
			//有的情况是更新，所以这里改成save，chenhb 20170804
			feeCompanyModel.save(feeCompany);
		} catch (Exception e) {
			LOG.error("出现了一个未知的错误  plainTextJson:" + JSON.toJSONString(plainTextJson), e);
		}
		return true;

	}
    /**
     * fee>0.1时，isPay=1
     *
     * fee<0.1时
     * 时间小于20天且金额为0，isPay=0（试用）
     * 时间大于20天小于10年且金额为0，isPay=1（赠送）
     * 时间大于50年且金额为0，isPay=0（免费）
     * @param plainTextJson
     * @param feeCompany
     * 创建时间 2021/10/28 7:29 PM
     * 修改时间 2021/10/28 7:29 PM
     * @author chy
     */
    private void setIsPay(JSONObject plainTextJson, FeeCompanyEntity feeCompany) {
        // 支付金额，以分为单位
        Float payFee = plainTextJson.getFloat("payFee");
        payFee=payFee ==null?0:payFee;
        Float fee = payFee/100f;
        if (fee > 0.1f) {
            feeCompany.setIsPay(1);
            return ;
        }
        // startTime
        Long paidtime = plainTextJson.getLong("paidtime")/1000L;
        // endTime
        Long serviceStopTime = plainTextJson.getLong("serviceStopTime")/1000L;
        //实际是多少月可用
        Long dayNum=( serviceStopTime -paidtime )/86400;
        Long monthNum = dayNum/30;
        Integer isPay = 0;
//		国波需求
//		1、时间小于20天且金额为0，则设置为未支付（试用）
//		2、时间大于20天小于10年且金额为0，则设置为已支付（赠送）
//		3、时间大于50年且金额为0，则设置为未支付（免费）
        if (dayNum < 20L) {
            //时间小于20天且金额为0，则设置为未支付（试用）
            isPay = 0;
        } else if (dayNum > 20L && monthNum < 120L) {
            //时间大于20天小于10年且金额为0，则设置为已支付（赠送）
            isPay = 1;
        } else if (monthNum > 600L) {
            //时间大于50年且金额为0，则设置为未支付（免费）
            isPay = 0;
        }
        feeCompany.setIsPay(isPay);
    }

	/**
	 * 钉钉内购原始订单保存
	 * @param plainTextJson
	 * @return
	 */
	private boolean dingTalkPay(JSONObject plainTextJson) {
		String pushRobotUrl = null;
		String corpName = null;
		Long subQuantity = plainTextJson.getLong("subQuantity") == null ? BasicConstant.ZERO_LONG : plainTextJson.getLong("subQuantity");
		String itemName = plainTextJson.getString("itemName");
		String goodsName = plainTextJson.getString("goodsName");
		String buyCorpId = plainTextJson.getString("buyCorpId");

		try {
			DingTalkPayLogEntity dingTalkPayLogEntity = new DingTalkPayLogEntity();

			String orderId = plainTextJson.getString("orderId");
			String itemCode = plainTextJson.getString("itemCode");
			// 支付金额，以分为单位
			Float payFee = plainTextJson.getFloat("payFee");
			// endTime
			Long serviceStopTime = plainTextJson.getLong("serviceStopTime")/1000;
			String appId = plainTextJson.getString("appId");
			String goodsCode = plainTextJson.getString("goodsCode");
			// startTime
			Long paidtime = plainTextJson.getLong("paidtime")/1000;
			String SuiteKey = plainTextJson.getString("SuiteKey");

			payFee = payFee == null ? 0 : payFee;
			// 转换为元 数据库默认保存7位小数
			Float fee = payFee/100f;

			CompanyEntity company = companyModel.getByKey(buyCorpId);
			//公司不存在
			if (company == null) {
				LOG.info("该订单："+ orderId + "对应的公司id不存在：" + buyCorpId);
				// 临时备注一下特殊情况
				StringBuffer memoBuffer = new StringBuffer();
				memoBuffer.append("该订单：").append(orderId).append("对应的公司id不存在：").append(buyCorpId);
				dingTalkPayLogEntity.setOwnerMemo(memoBuffer.toString());
			}
			// 内购套餐
			Map<String, Object> params = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
			params.put("xinxuanProductCode", goodsCode);
			params.put("xinxuanSkuId", itemCode);
			params.put("del", DelEnum.NORMAL.getDel());
			params.put("start", BasicConstant.ZERO);
			params.put("pageNum", BasicConstant.ONE);
			List<FeeEntity> feeEntityList = feeModel.findEntitys(params);
			FeeEntity feeEntity = null;
			if(CollectionUtils.isNotEmpty(feeEntityList)){
				feeEntity = feeEntityList.get(0);
			}
			Integer orderType = null;
			Long feeId = null;
			if (feeEntity == null) {
				LOG.info("该订单是内购订单："+ orderId+ ",但fee表中不存在goodsCode：" + goodsCode + "以及itemCode：" + itemCode);
				// 临时备注一下特殊情况
				StringBuffer feeBuffer = new StringBuffer();
				feeBuffer.append("该订单是内购订单：").append(orderId).append(",但fee表中不存在goodsCode：").append(goodsCode).append("以及itemCode：").append(itemCode);
				dingTalkPayLogEntity.setOwnerMemo(feeBuffer.toString());
			} else {
				feeId = feeEntity.getId();
				Integer feeType = feeEntity.getType();
				if(DingTalkPayConstant.CONSUME_FEE.contains(feeType)){
					orderType = BasicConstant.ONE;
				}
				if(DingTalkPayConstant.CYCLE_FEE.contains(feeType)){
					orderType = BasicConstant.ZERO;
				}
				pushRobotUrl = feeEntity.getPushRobotUrl();
			}

			// 内购订单表
			corpName = company == null ? "" : company.getCorpName();
			dingTalkPayLogEntity.setCorpid(buyCorpId);
			dingTalkPayLogEntity.setCorpName(corpName);
			dingTalkPayLogEntity.setFeeId(feeId);
			dingTalkPayLogEntity.setOrderType(orderType);
			dingTalkPayLogEntity.setOrderId(orderId);
			dingTalkPayLogEntity.setBuyNum(subQuantity.intValue());
			dingTalkPayLogEntity.setSurplusNum(subQuantity.intValue());
			dingTalkPayLogEntity.setItemCode(itemCode);
			dingTalkPayLogEntity.setItemName(itemName);
			dingTalkPayLogEntity.setPayFee(fee);
			dingTalkPayLogEntity.setServiceStopTime(serviceStopTime);
			dingTalkPayLogEntity.setGoodsCode(goodsCode);
			dingTalkPayLogEntity.setGoodsName(goodsName);
			dingTalkPayLogEntity.setPayTime(paidtime);
			dingTalkPayLogEntity.setFinishStatus(BasicConstant.ZERO);
			dingTalkPayLogModel.save(dingTalkPayLogEntity);
		} catch (Exception e) {
			LOG.error("出现了一个未知的错误  plainTextJson:" + JSON.toJSONString(plainTextJson), e);
		}

		// 异步机器人推送群消息
		if(pushRobotUrl != null){
			String finalPushRobotUrl = pushRobotUrl;
			String finalCorpName = corpName;
			Runnable runnable = () -> {
				LOG.info("获取群推送机器人地址",finalPushRobotUrl);
				try {
					Long timestamp = System.currentTimeMillis();
					// finalPushRobotUrl = "https://oapi.dingtalk.com/robot/send?access_token=4e0191c6ace33f279116a5e807e87875bf4837ebff74d01cbb432e234ceee1cb&secret=SECc0bff313b8e2c8ce8a992b22798e6127e628794c2fd29cd6df626a4dff13c744";
					String[] split = finalPushRobotUrl.split("&secret=");
					String postUrl = split[0];
					String secret = split[1];

					String stringToSign = timestamp + "\n" + secret;
					Mac mac = Mac.getInstance("HmacSHA256");
					mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
					byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
					String sign = URLEncoder.encode(new String(Base64.encodeBase64(signData)),"UTF-8");

					String pushPostUrl = postUrl + "&timestamp=" + timestamp + "&sign=" + sign;
					LOG.info("钉钉内购机器人推送地址:" + pushPostUrl);
					DingTalkClient client = new DefaultDingTalkClient(pushPostUrl);
					OapiRobotSendRequest request = new OapiRobotSendRequest();
					request.setMsgtype("text");
					OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
					text.setContent("公司："+ finalCorpName + ",购买了"+subQuantity+"条“"+goodsName+"-"+itemName+"”。");
					request.setText(text);
					OapiRobotSendResponse response = client.execute(request);
					LOG.info("钉钉内购机器人推送回参:" + response);
				}catch (Exception e) {
					LOG.error("钉钉内购机器人推送群消息", e);
				}
			};
			threadPoolBeanConfig.sendRobotMsgThreadPool().execute(runnable);
		}
		return true;
	}

	/**
	 * 免费试用时，设置关键字段
	 * @param plainTextJson
	 * @param feeCompany
	 * 创建时间 2021/2/3 6:56 PM
	 * 修改时间 2021/2/3 6:56 PM
	 * @author chy
	 */
	private void freePackage(JSONObject plainTextJson, FeeCompanyEntity feeCompany) {
		//FREE(对应：免费开通)，TRYOUT(对应试用开通)，CHARGE(对应付费开通)
		String orderChargeType = plainTextJson.getString("orderChargeType");
        // endTime
		Long serviceStopTime = plainTextJson.getLong("serviceStopTime")/1000;
		//传过来的免费版标识
		boolean isFree = Objects.equals(orderChargeType, "FREE");
		Long now = DateTimeUtil.getInt();
		Long ninetyYear = 90 * 365 * 86400L;
		//免费版传过来的有效时间为100年，此处当有效时间大于90年，就认为是免费版
		boolean isFreeTime = (serviceStopTime - now) > ninetyYear;
		if (!isFree && !isFreeTime) {
			return;
		}
		// startTime
		Long paidtime = plainTextJson.getLong("paidtime")/1000 ;
		//实际是多少月可用
		Long dayNum=( serviceStopTime -paidtime )/86400;
		Long monthNum = dayNum/30;
		feeCompany.setFeeType(PackageTypeEnum.FREE.getType());
		feeCompany.setFeeName("解决方案免费版");
		feeCompany.setRealPrice(0F);
		feeCompany.setBuyNum(1);
		feeCompany.setFee(0F);
		feeCompany.setUserNum(2);
		feeCompany.setDayNum(monthNum.intValue());
		feeCompany.setStartTime(paidtime);
		feeCompany.setEndTime(serviceStopTime);
	}

	/**
	 * 应用试用记录回调(个人试用)
	 * 1，告知销售这是个人试用，而非组织试用，找到最新那条fee_company,写到auth_channel,=“个人试用”
	 * 2，如果是个人试用，把tb_user，第一条del=0员工表里这个公司的员工设置为 销帮帮管理员，role_ids=|1|
	 * @param plainTextJson
	 * @return
	 */
	private boolean personalTryOut(JSONObject plainTextJson) {
		try {
			String corpId = plainTextJson.getString("corpId");

			String fromDate = plainTextJson.getString("fromDate");
			String endDate = plainTextJson.getString("endDate");
			String buyeruserId = plainTextJson.getString("buyerUnionId");
			String userid = plainTextJson.getString("userid");

			userid = userid == null ? "0" : userid;
			Long start = fromDate == null ? DateTimeUtil.getInt() : DateTimeUtil.getInt(fromDate, DateUtil.SDF);
			Long end = fromDate == null ? start + 15 * 86400 : DateTimeUtil.getInt(endDate, DateUtil.SDF);

			FeeCompanyEntity feeCompanyEntity = new FeeCompanyEntity();
			CompanyEntity companyEntity = companyModel.getByKey(corpId);
			String corpName = companyEntity == null ? "" : companyEntity.getCorpName();
			feeCompanyEntity.setCorpName(corpName);
			feeCompanyEntity.setCorpid(corpId);
			// 成员授权（个人试用的升级版） 成员试用时corid是真实corpid
			feeCompanyEntity.setMainCorpid(corpId);
			feeCompanyEntity.setFeeId(0);
			feeCompanyEntity.setFeeName("个人试用");
			feeCompanyEntity.setRealPrice(0F);
			feeCompanyEntity.setBuyNum(1);
			feeCompanyEntity.setFee(0F);
			// 0标准版  1高级版 3旗舰版
			feeCompanyEntity.setFeeType(0);
			feeCompanyEntity.setUserNum(100000);
			feeCompanyEntity.setDayNum(0);
			feeCompanyEntity.setIsPay(0);
			feeCompanyEntity.setIsActive(1);
			feeCompanyEntity.setIsInvoice(0);
			feeCompanyEntity.setOpUserName("");
			feeCompanyEntity.setOrderId("");
			feeCompanyEntity.setPayTime(DateTimeUtil.getInt()); // 毫秒的话除1000

			feeCompanyEntity.setPayType(2);// 钉钉支付

			feeCompanyEntity.setAuthChannel("个人试用");
			feeCompanyEntity.setStartTime(start);
			feeCompanyEntity.setEndTime(end);
			feeCompanyEntity.setMemo("开通个人试用");
			feeCompanyEntity.setAddTime(DateUtil.getInt());
			feeCompanyEntity.setUpdateTime(DateUtil.getInt());
			feeCompanyEntity.addMarketBuyJson(plainTextJson.toJSONString());
			feeCompanyModel.insert(feeCompanyEntity);

			// 成员授权可以有多人 将操作人设为超管
			UserEntity userEntity;
			userEntity = userModel.getByKey(userid, corpId);
			if (Objects.isNull(userEntity)){
				LOG.error("---personal_tryout---userEntity is null");
				// 个人试用时，公司可能没有超管，需要修改第一人为超管
				Map<String, Object> param = new HashMap<>();
				param.put("del", 0);
				param.put("corpid", corpId);
				param.put("start", 0);
				param.put("pageNum", 1);
				param.put("orderByStr", "add_time desc");
				List<UserEntity> userlist = userModel.findEntitys(param);
				if(!userlist.isEmpty()){
					userEntity = userlist.get(0);
				}else{
					return false;
				}
			}
			userEntity.setRoleIds(RoleEnum.ADMIN.getAlias());
			userModel.update(userEntity);

		} catch (Exception e) {
			LOG.error("personalTryOut事件出现了一个未知的错误  plainTextJson:" + JSON.toJSONString(plainTextJson), e);
		}
		return true;
	}


	/**
	 * 刷新数据库company表中的公司持久码
	 * @param request
	 * @return
	 * @throws IOException
	 * kaka
	 * 2016年12月22日 下午5:19:10
	 */
	@ResponseBody
	@RequestMapping("/freshPermanentCode")
	public String freshPermanentCode(HttpServletRequest request) throws IOException {
		JSONObject retJson = new JSONObject();
		String key = request.getParameter("key");
		if (!BasicConstant.TASK_SECRET_KEY.equals(key)) {
			retJson.put("code", 3);
			retJson.put("msg", "k wrong");
			return retJson.toJSONString();
		}

		//所有数据
		Map<String, Object> map = new HashMap<>();

		//总数
		Integer allCount = companyModel.getEntitysCount(map);
		Integer start = 0;
		//每次查询5000条
		Integer pageNum = 5000;
		for(;start <= allCount; start = start + pageNum){
			map.put("start", start);
			map.put("pageNum", pageNum);
			List<CompanyEntity> list = companyModel.findEntitys(map);
			for(CompanyEntity company : list) {
				checkPermanentCode(company);
			}
			//有助于回收内存
			list = null;
		}

		retJson.put("code", 1);
		retJson.put("msg", "执行完毕");
		return retJson.toJSONString();
	}


	/**
	 * 检查公司的数据库中是否保存了持久码，若没有则从缓存中取
	 * @param company
	 * kaka
	 * 2016年12月22日 下午5:35:44
	 */
	private void checkPermanentCode(CompanyEntity company) {
		if(company == null) {
			return;
		}

		//默认不需要更新公司实体
		boolean flag = false;
		String permanent_code = paasRedisHelper.getValue("permanentcode", company.getCorpid());

		if(!StringUtil.isEmpty(permanent_code)) {
			if(StringUtil.isEmpty(company.getPermanentCode())) {
				company.setPermanentCode(permanent_code);
				flag = true;
			} else {
				if(!permanent_code.equals(company.getPermanentCode())) {
					LOG.info("发现一个公司的缓存持久码与数据库持久码不一致，corpid=" + company.getCorpid() + "缓存中为:" + permanent_code + ";数据库中为:" + company.getPermanentCode());
					company.setPermanentCode(permanent_code);
					flag = true;
				}
			}
		} else {
			if(StringUtil.isEmpty(company.getPermanentCode())) {
				LOG.info("发现一个公司的持久码在缓存中和数据库中都为空，确认是否是已解绑或停用的客户:corpid == " + company.getCorpid());
			}
		}

		if(flag) {
			LOG.info("更新了公司corpid=" + company.getCorpid() + "的持久码");
			try {
				companyModel.update(company);
			} catch (XbbException e) {
				LOG.error("更新公司信息出错", e);
			}
		}
	}

    /**
     * 执行脚本 钉钉可见范围 同步到CompanyConfig表
	 * 脚本已关闭
     *
     * @param request HttpServletRequest 请求体
     */
    @RequestMapping("/syncCompanyConfig")
    public String syncCompanyConfig(HttpServletRequest request) {

		XbbResponse<BaseVO> response;
        String key = request.getParameter("key");
        if (!BasicConstant.TASK_SECRET_KEY.equals(key)) {
			response = new XbbResponse<>(SystemErrorCodeEnum.API_ERROR_100002, "访问密钥错误");
            return JSON.toJSONString(response);
        }

        //关闭同步脚本，以免不必要的问题出现
        if(true) {
			response = new XbbResponse<>(SystemErrorCodeEnum.API_ERROR_100001, "该接口已关闭");
			return JSON.toJSONString(response);
		}
        //单个公司
       String corpidUpdate = request.getParameter("corpid");
		// all 全公司
       String range = request.getParameter("range");
		/*
		 * 异步更新公司信息
		 */
		syncCompanyThread(corpidUpdate, range);
		response = new XbbResponse<>(new BaseVO());
		return JSON.toJSONString(response);
    }

	/**
	 * 手动触发组织架构同步接口
	 */
	@RequestMapping("/syncDepartmentUser")
	public String syncOrganization(@RequestBody @Valid SyncOrganizationDTO syncOrganizationDTO, BindingResult br) throws Exception {
		XbbResponse<BaseVO> response;
		if (br.hasErrors()) {
			String errMsg = Objects.requireNonNull(br.getFieldError()).getDefaultMessage();
			response = new XbbResponse<>(SystemErrorCodeEnum.API_ERROR_100002, errMsg);
		} else {
			try {
				BaseVO vo = isvReceiveService.syncOrganization(syncOrganizationDTO);
				response = new XbbResponse<>(vo);
			} catch (XbbException e) {
				response = new XbbResponse<>(e);
			}
		}
		return JSON.toJSONString(response);
	}

	/**
	 * 手动触发组织架构同步接口(admin及其他方式get触发)
	 * @param syncOrganizationForAdminDTO
	 * @param br
	 * @return
	 * 创建时间 2019/8/13 4:11 PM
	 * 修改时间 2019/8/13 4:11 PM
	 * @author chy
	 * @since v1.1.3
	 * @version v1.1.3
	 */
	@RequestMapping(value = "/syncDepartmentUserForAdmin", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
	public String syncDepartmentUserForAdmin(@Valid SyncOrganizationForAdminDTO syncOrganizationForAdminDTO, BindingResult br) {
		XbbResponse<BaseVO> response;
		if (br.hasErrors()) {
			String errMsg = Objects.requireNonNull(br.getFieldError()).getDefaultMessage();
			response = new XbbResponse<>(SystemErrorCodeEnum.API_ERROR_100002, errMsg);
		} else {
			try {
				String key = syncOrganizationForAdminDTO.getKey();
				if (!BasicConstant.TASK_SECRET_KEY.equals(key)) {
					response = new XbbResponse<>(SystemErrorCodeEnum.API_ERROR_100002, "访问密钥错误");
					return JSON.toJSONString(response);
				}

				SyncOrganizationDTO syncOrganizationDTO = new SyncOrganizationDTO();

				BeanUtil.copyProperties(syncOrganizationForAdminDTO, syncOrganizationDTO);

				BaseVO vo = isvReceiveService.syncOrganization(syncOrganizationDTO);
				response = new XbbResponse<>(vo);
			} catch (XbbException e) {
				response = new XbbResponse<>(e);
			}
		}
		return JSON.toJSONString(response);
	}

	/**
	 * 同步公司信息
	 * @author kaka
	 * @time 2016年7月27日 下午2:54:54
	 */
	@RequestMapping("/syncCompanyInfo")
	public String syncCompanyInfo(HttpServletRequest request){
		XbbResponse<BaseVO> response;
		String updateCorpid = request.getParameter("updateCorpid");
		String addTimeStart = request.getParameter("startTime");
		String key = request.getParameter("key");
		if (!BasicConstant.TASK_SECRET_KEY.equals(key)) {
			response = new XbbResponse<>(SystemErrorCodeEnum.API_ERROR_100002, "访问密钥错误");
			return JSON.toJSONString(response);
		}

		// 获取当前的suiteToken
		Object value = paasRedisHelper.getSuiteTokenValue(RedisPrefixConstant.DINGTALK_TICKET, "suiteToken");
		if (value == null) {
			response = new XbbResponse<>(SystemErrorCodeEnum.API_ERROR_100001, "suiteToken出错");
			return JSON.toJSONString(response);
		}
		String suiteTokenPerm = value.toString();
		if (StringUtil.isEmpty(updateCorpid) && StringUtil.isEmpty(addTimeStart)) {
			response = new XbbResponse<>(SystemErrorCodeEnum.API_ERROR_100002, "参数缺失");
			return JSON.toJSONString(response);
		}

		if (!StringUtil.isEmpty(addTimeStart)) {
			int addTimeStartInt;
			try {
				addTimeStartInt = Integer.valueOf(addTimeStart);
			} catch (Exception e) {
				LOG.error("addTimeStart参数错误", e);
				response = new XbbResponse<>(SystemErrorCodeEnum.API_ERROR_100002, "addTimeStart参数错误");
				return JSON.toJSONString(response);
			}

			Map<String, Object> param = new HashMap<String, Object>();
			int start = 0;
			int pageNum = 100;
			List<CompanyEntity> companys = new ArrayList<CompanyEntity>();
			do {
				param.put("addTimeStart", addTimeStartInt);
				param.put("start", start);
				param.put("pageNum", pageNum);

				companys = companyModel.findEntitys(param);

				for (CompanyEntity company : companys) {
					companyModel.updateCompanyInfo(company.getCorpid(), suiteTokenPerm);
				}

				start += pageNum;
			} while (companys.size() == pageNum);

		} else if (!StringUtil.isEmpty(updateCorpid)) {
			companyModel.updateCompanyInfo(updateCorpid, suiteTokenPerm);
		}

		response = new XbbResponse<>(new BaseVO());
		return JSON.toJSONString(response);
	}

	/**
	 * 删除组织架构同步过程中没有更新的数据，即不在授权范围内或已删除的数据
	 * @param corpid 公司id
	 * @param fetchStartTime 开始同步员工数据的开始时间(user&userDepartment表)
	 */
	private void deleteInfoAfterSync(final String corpid, final Integer fetchStartTime) {
		// 删除员工
		userModel.clearUnauthUserByUpdateTime(corpid, fetchStartTime);
		// 删除员工部门关系
		userDepartmentModel.clearUnauthUserByUpdateTime(corpid, fetchStartTime);
	}

	/**
	 * 插入公司节点
	 * @param corpid
	 * @param corpName
	 * kaka
	 * 2016年12月27日 下午8:38:15
	 */
	private void InsertCompany1stDept(String corpid, String corpName) throws XbbException {
		DepartmentEntity fstDept = new DepartmentEntity();
		fstDept.setAutoAddUser(0);
		fstDept.setCorpid(corpid);
		fstDept.setCreateDeptGroup(0);
		fstDept.setDepIdRouter("|1|");
		fstDept.setDeptHiding(0);
		fstDept.setDeptManagerUseridList("");
		fstDept.setDeptPerimits("");
		fstDept.setId(1L);
		fstDept.setName(corpName);
		fstDept.setOrgDeptOwner("");
		fstDept.setOuterDept(0);
		fstDept.setOuterPermitDepts("");
		fstDept.setOuterPermitUsers("");
		fstDept.setParentId(0L);
		fstDept.setSort(0);
		fstDept.setUserPerimits("");
		departmentModel.insert(fstDept);
	}

	@ResponseBody
	@RequestMapping("/redisDelete")
	public String redisDelete(HttpServletRequest request) throws Exception {
		String corpid = request.getParameter("corpid");
		JSONObject syncStatus = new JSONObject();
		syncStatus.put("code", 4);
		syncStatus.put("msg", "组织架构同步失败");
		paasRedisHelper.setValue(DEPARTMENT_REFRESH_STATUS, corpid, syncStatus, SYNC_STATUS_TIME);
		return "{\"corpid\", \"" + corpid + "\"}";
	}

	/**
	 * 保存或更新用户实体
	 * @param corpId
	 * @param userId
	 * @param depId
	 * @param user
	 * @param userInDBMap
	 * kaka
	 * 2016年12月28日 上午10:18:59
	 */
	private void saveUser(String corpId, String userId, Long depId, UserEntity user, Map<String, UserEntity> userInDBMap) throws XbbException {
		if(user == null) {
			//user不能为null
			return;
		}
		boolean insertUser = false;
		if (!userInDBMap.containsKey(userId)) {
			user.setCorpid(corpId);
			insertUser = true;
		} else {
			UserEntity oldUser = userInDBMap.get(userId);
			user.setAddTime(oldUser.getAddTime());
			user.setRoleIds(oldUser.getRoleIds());
			user.setIsPushMessage(oldUser.getIsPushMessage());
			user.setPassportId(oldUser.getPassportId());
			user.setCorpid(corpId);
		}
		// 设置del为0
		user.setDel(0);
		if(StringUtil.isEmpty(user.getCorpid())) {
			user.setCorpid(corpId);
		}
		// 此处id不会是0所以不要用save
		if (insertUser) {
			userModel.insert(user);
		} else {
			userModel.update(user);
		}
		// 保存用户部门关系表数据
		saveUserDeparment(user, corpId);
	}

	/**
	 * 根据用户JSONObject构建用户实体，插入或者更新数据库记录
	 *
	 * @param corpId
	 * @param userId
	 * @param depId
	 *            为null时表示直接从用户详情接口获取的数据，而不是从部门员工详情列表获取的数据
	 * @param user
	 * @param userInDBMap
	 *            该公司数据库中所有用户信息，转化为<userId,userEntity>形式
	 */
	private void saveUser(String corpId, String userId, Long depId,
                          JSONObject user, Map<String, UserEntity> userInDBMap) throws XbbException {

		UserEntity userEntity = userInDBMap.get(userId);
		boolean insertUser = false;
		if (userEntity == null) {
			userEntity = new UserEntity();
			userEntity.setCorpid(corpId);
			insertUser = true;
		}
		userEntity.formatUserData(user, depId, insertUser);
		// 设置del为0
		userEntity.setDel(0);

		if(StringUtil.isEmpty(userEntity.getCorpid())) {
			userEntity.setCorpid(corpId);
		}
		// 此处id不会是0所以不要用save
		if (insertUser) {
			userModel.insert(userEntity);
		} else {
			userModel.update(userEntity);
		}
		// 保存用户部门关系表数据
		saveUserDeparment(userEntity, corpId);
	}

	/**
	 * 保存用户部门关系表数据
	 *
	 * @param user
	 * @param corpId
	 */
	private void saveUserDeparment(UserEntity user, String corpId) throws XbbException {
		if (user == null || StringUtil.isEmpty(corpId)) {
			return;
		}

		String userId = user.getUserId();
		if (StringUtil.isEmpty(userId)) {
			return;
		}

		Map<Long, UserDepartmentEntity> userDeptsMap = new HashMap<Long, UserDepartmentEntity>();

		Map<String, Object> param = new HashMap<String, Object>();
		param.put("corpid", corpId);
		param.put("userId", userId);

		List<UserDepartmentEntity> userDepts = userDepartmentModel.findEntitys(param);
		if (userDepts.size() > 0) {
			for (UserDepartmentEntity userDept : userDepts) {
				userDeptsMap.put(userDept.getDepartmentId(), userDept);
			}
		}

		String orderInDepts = user.getOrderInDepts();
		String isLeaderInDepts = user.getIsLeaderInDepts();
		String department = user.getDepartment();
		// 解析
		JSONArray depIds = JSONArray.parseArray(department);
		Map<Long, Long> orderInDeptsJson = JSON.parseObject(orderInDepts,
				new TypeReference<Map<Long, Long>>() {
				});
		Map<Long, Boolean> isLeaderInDeptsJson = JSON.parseObject(
				isLeaderInDepts, new TypeReference<Map<Long, Boolean>>() {
				});

		for (int i = 0; i < depIds.size(); i++) {
			Long depId = depIds.getLong(i);

			Long sort = orderInDeptsJson.get(depId);
			int isLeader = isLeaderInDeptsJson.get(depId) ? 1 : 0;

			UserDepartmentEntity userDept = userDeptsMap.get(depId);
			boolean userDeptInsert = false;
			if (userDept == null) {
				userDept = new UserDepartmentEntity();
				userDeptInsert = true;
			}
			userDept.setCorpid(corpId);
			userDept.setUserId(userId);
			userDept.setDepartmentId(depId);
			userDept.setIsLeader(isLeader);
			userDept.setSort(sort);

			// 更新修改时间
			userDept.setUpdateTime(DateUtil.getInt());
			// 将删除标记置为0
			userDept.setDel(0);

			if (userDeptInsert) {
				userDepartmentModel.insert(userDept);
			} else {
				userDepartmentModel.update(userDept);
			}
		}

	}

	/**
	 * 从数据库中查询出公司所有user信息并放入内存
	 *
	 * @param corpId
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private Map<String, UserEntity> getAllUserByCorpId(String corpId) {
		Map<String, UserEntity> userMap = new HashMap<String, UserEntity>();
		Integer pageSize = 1000;//每页1000条
		Integer page = 0;
		List<UserEntity> list;
		do {
			page++;
			Map<String, Object> param = new HashMap<String, Object>();
			param.put("corpid", corpId);
			param.put("page", page);

			PageHelper pageHelper = null;
			pageHelper = PageHelperUtil.getPageHelper(param, userModel, pageSize);
			list = (List<UserEntity>) PageHelperUtil.getEntityList(param, pageHelper,
					userModel);
			if (list.size() > 0) {
				for (UserEntity user : list) {
					userMap.put(user.getUserId(), user);
				}
			}else{
				break;
			}
		} while (list.size() == pageSize);
		return userMap;
	}

	@ResponseBody
	@RequestMapping("/dingtalk/testMarketBuy")
	public String testMarketBuy(HttpServletRequest request,
                                HttpServletResponse response, Map<String, Object> modelMap)
			throws Exception {
		// 开始时间
		String plainTextJson = "{ \"EventType\": \"market_buy\","
			 +"   \"SuiteKey\": \"suitexdhgv7mn5ufoi9ui\","
			 +"   \"buyCorpId\": \"dingaba5244867492d3935c2f4657eb6378f1\","
			 +"   \"goodsCode\": \"FW_GOODS-1000258333\","
			 +"   \"itemCode\": \"1c5f70cf04c437fb9aa1b209602cc886\","
			 +"   \"itemName\": \"一年套餐19人以下\","
			 +"   \"maxOfPeople\": 300,"
			 +"   \"minOfPeople\": 0,"
			 +"   \"orderId\": 308356401790007,  "
			 +"   \"paidtime\": 1474535702000,   "
			 +"   \"distributorCorpId\": \"dinge3fa697f86d461d2\",   "
			 +"   \"distributorCorpName\": \"杭州逍邦网络科技有限公司\",   "
			 +"   \"serviceStopTime\": 1477065600000  }";
		boolean res=marketBuy(JSONObject.parseObject(plainTextJson));
		modelMap.put("res", res);
		modelMap.put("code", 1);
		return JSON.toJSONString(modelMap);
	}

	@ResponseBody
	@RequestMapping("/testRobotMsg")
	public String testRobotMsg(HttpServletRequest request, HttpServletResponse response, Map<String, Object> modelMap) throws Exception {
		Long timestamp = System.currentTimeMillis();
		String finalPushRobotUrl = "https://oapi.dingtalk.com/robot/send?access_token=4e0191c6ace33f279116a5e807e87875bf4837ebff74d01cbb432e234ceee1cb&secret=SECc0bff313b8e2c8ce8a992b22798e6127e628794c2fd29cd6df626a4dff13c744";
		String[] split = finalPushRobotUrl.split("&secret=");
		String postUrl = split[0];
		String secret = split[1];

		String stringToSign = timestamp + "\n" + secret;
		Mac mac = Mac.getInstance("HmacSHA256");
		mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
		byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
		String sign = URLEncoder.encode(new String(Base64.encodeBase64(signData)),"UTF-8");

		DingTalkClient client = new DefaultDingTalkClient(postUrl + "&timestamp=" + timestamp + "&sign=" + sign);
		OapiRobotSendRequest sendRequest = new OapiRobotSendRequest();
		sendRequest.setMsgtype("text");
		OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
		text.setContent("新增"+1+"条“"+"周期规格6"+"-"+"内购商品AA"+"”的购买信息，部署人员请火速前往现场");
		sendRequest.setText(text);
		OapiRobotSendResponse sendResponse = client.execute(sendRequest);
		LOG.info("钉钉内购机器人推送回参:" + sendResponse);
		modelMap.put("res", sendResponse.getErrmsg());
		modelMap.put("code", sendResponse.getErrcode());
		return JSON.toJSONString(modelMap);
	}

    /**
     * 从钉钉获取可见范围，保存到CompanyConfig表
     * @param corpid
     */
    private void syncCompanyConfigScript(String corpid){
        try {
        	LOG.info("开始同步可见范围corpid: " + corpid);
            String accessToken = authHelper.getAccessToken(corpid);
            // agentId
            String agentId = authHelper.getAgentId(corpid, Env.DING_XBB_APP_ID);

            JSONObject retJson = null;
            try {
                retJson = MicroAppHelper.geVisibleScopes(accessToken, agentId);
            } catch (OApiException e) {
                LOG.error("syncCompanyConfig  MicroAppHelper.geVisibleScopes出错", e);
                return;
            }

            Integer errcode = retJson.getInteger("errcode");
			// 出错
            if (errcode != null && errcode.equals(0)) {
                LOG.error("syncCompanyConfig  MicroAppHelper.geVisibleScopes出错  retJosn = " + retJson.toJSONString());
                return;
            }
            /*
             * 以下几种情况都是全公司:
             * {"deptVisibleScopes":[],"isHidden":false,"userVisibleScopes":[]}
             * 没有选任何部门和人 {"deptVisibleScopes":"1","isHidden":false} 微应用添加时选了全公司
             * {"deptVisibleScopes":[1],"isHidden":false,"userVisibleScopes":[]}
             * 修改微应用可见范围时选择第一级公司
             * {"deptVisibleScopes":[6604589,6604590,6604591,6616759
             * ,9268861,1],"isHidden"
             * :false,"userVisibleScopes":["0440552440847317","044626310037745767"]}
             * 修改微应用可见范围时选择第一级公司和其他几个部门或员工
             */

			// 微应用是否全公司可见
            boolean entireCompany = false;
            String deptVisibleScopes = retJson.getString("deptVisibleScopes");
            String userVisibleScopes = retJson.getString("userVisibleScopes");
            if (Objects.equals(deptVisibleScopes, "1")) {
                entireCompany = true;
            }

            List<Long> depIdIn;
            try {
                depIdIn = JSON.parseArray(deptVisibleScopes, Long.class);
            } catch (Exception e) {
                LOG.error("/dingtalk/syncCompanyConfig deptVisibleScopes List<Long>转化异常", e);
                // 这里转换出错先当做全公司处理
                depIdIn = new ArrayList<>();
            }

            if (depIdIn == null) {
                depIdIn = new ArrayList<>();
            }

            List<String> userVisibleScopesList;
            try {
                userVisibleScopesList = JSON.parseArray(userVisibleScopes, String.class);
            } catch (Exception e) {
                LOG.error("/dingtalk/syncCompanyConfig userVisibleScopes List<String>转化异常" + userVisibleScopes, e);
                userVisibleScopesList = new ArrayList<>();
            }

            if (userVisibleScopesList == null) {
                userVisibleScopesList = new ArrayList<>();
            }

            if (depIdIn.contains(1L) || (depIdIn.size() == 0 && userVisibleScopesList.size() == 0)) {
                entireCompany = true;
            }

            CompanyConfigEntity companyConfig = companyConfigModel.getConfigEntity(corpid,"companyVisibleScopes");
            if (null == companyConfig) {
                companyConfig = new CompanyConfigEntity();
                companyConfig.setConfigAlias("companyVisibleScopes");
                companyConfig.setConfigName("公司可见范围");
                companyConfig.setCorpid(corpid);
            }

            JSONObject valueJson = new JSONObject();
			//全公司
            if(entireCompany){
                JSONObject authScopes = null;
                try {
                    authScopes = MicroAppHelper.getAuthScopes(accessToken);
                } catch(OApiResultException e) {
                    LOG.error("获取通讯录权限时出错", e);
                }

                if( authScopes != null) {
                    JSONObject authOrgScopes = authScopes.getJSONObject("auth_org_scopes");
                    if(authOrgScopes != null) {
                        List<Long> authedDept = JSON.parseArray(authOrgScopes.getString("authed_dept"), Long.class);
                        List<String> authedUser = JSON.parseArray(authOrgScopes.getString("authed_user"), String.class);
                        valueJson.put("deptVisibleScopes",authedDept);
                        valueJson.put("userVisibleScopes",authedUser);
                    }
                }else {//不会有这样的情况
                    valueJson.put("deptVisibleScopes","[1]");
                    valueJson.put("userVisibleScopes","[]");
                }

            }else {
                valueJson.put("deptVisibleScopes",depIdIn);
                valueJson.put("userVisibleScopes",userVisibleScopesList);
            }
            companyConfig.setConfigValue(valueJson.toJSONString());
            companyConfigModel.save(companyConfig);

            //清理空部门，设置del=2
            departmentModel.clearEmptyDepartment(corpid);
			LOG.info("同步可见范围结束，corpid: " + corpid);
        } catch (Exception e){
			LOG.info("同步可见范围出错，corpid: " + corpid, e);
        }
    }

	/**
	 * 手动触发公司信息更新
	 * @param corpidUpdate 需要更新公司信息的公司id
	 * @param range 更新范围
	 */
	@Async(value = "manualSyncOrgPool")
	void syncCompanyThread(String corpidUpdate, String range) {
		//单个公司
		if(!StringUtil.isEmpty(corpidUpdate)){
			CompanyEntity company = companyModel.getByKey(corpidUpdate);
			if(company.getSource().equals(1) && company.getStatus().equals(1) && company.getDel().equals(0)){
				//同步脚本
				syncCompanyConfigScript(corpidUpdate);
			}
		}else if(!StringUtil.isEmpty(range) && "all".equals(range)){
			//全公司
			Map<String, Object> param = new HashMap<>(16);
			param.put("del", 0);
			param.put("source", 1);
			param.put("status", 1);
			int total = companyModel.getEntitysCount(param);
			int pageSize = 1000;

			for(int i = 0; i < total; i += pageSize){
				param.clear();
				param.put("del", 0);
				param.put("source", 1);
				param.put("status", 1);
				param.put("start",i);
				param.put("pageNum",pageSize);
				param.put("columns","corpid");
				List<CompanyEntity> companyEntityList = companyModel.findEntitys(param);
				for(CompanyEntity company :companyEntityList){
					String corpid = company.getCorpid();
					if (!StringUtil.isEmpty(corpid)) {
						//同步脚本
						syncCompanyConfigScript(corpid);
					}
				}
			}
			LOG.info("所有公司同步可见范围结束");
		}else{
			LOG.error("同步可见范围脚本链接异常");
		}
	}

	/**
	 * 处理应用的启用和停用回调
	 * @param opt 操作，只支持应用启用 org_micro_app_restore 和 应用停用 org_micro_app_stop
	 * @param plainTextJson 请求体解密后的JSONObject
	 */
	private void orgMicroAppSwitch(String opt, JSONObject plainTextJson) throws XbbException {
		if (!"org_micro_app_restore".equals(opt) && !"org_micro_app_stop".equals(opt)) {
			LOG.warn("opt参数错误，opt = " + opt);
			return;
		}

		// 使用该套件企业的corpid
		String corpId = plainTextJson.getString("AuthCorpId");
		String agentId = plainTextJson.getString("AgentId");
		if (StringUtil.isEmpty(agentId)) {
			LOG.warn(opt + " 回调: AgentId为空");
			return;
		}
		CompanyEntity company = companyModel.getByKeyIgnoreDel(corpId);
		if (company == null) {
			LOG.warn(opt + " 回调: 公司不存在");
			return;
		}
		if (!agentId.equals(company.getAgentId())) {
			LOG.warn(opt + " 回调: AgentId不一致");
			return;
		}

		if ("org_micro_app_restore".equals(opt)) {
			company.setStatus(1);
		} else {
			company.setStatus(0);
		}

		company.setUpdateTime(DateTimeUtil.getInt());
		companyModel.update(company);
		LOG.info(opt + " 回调 执行成功");
	}
}
